package com.panda.autotest.core.engine;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import com.alibaba.fastjson.JSON;
import com.panda.autotest.core.AutoTestConstants;
import com.panda.autotest.core.parse.CaseConfig;
import com.panda.autotest.core.parse.CaseConfigManager;
import com.panda.autotest.core.parse.Step;
import com.panda.autotest.core.parse.Type;
import com.panda.autotest.dao.CaseExtDao;
import com.panda.autotest.dao.model.Case;
import com.greenpineyu.fel.FelEngine;
import com.greenpineyu.fel.FelEngineImpl;
import com.greenpineyu.fel.context.FelContext;

@Component
public class CaseHandler {
	private Logger logger = LoggerFactory.getLogger(CaseHandler.class);
	@Value("${max.implicitlyWait}")
	private int implicitlyWait = 30;
	@Value("${max.unitWait}")
	private int unitWait = 10;
	@Value("${retry.wait.time}")
	private int retryWaitTime = 2000;
	@Value("${max.retry.sql.num}")
	private int maxRetrySqlNum = 3600;
	@Value("${submit.wait.camera}")
	private int submitWaitCamera = 1000;
	@Value("${skip.module.try.max.num}")
	private int skipModuleTryMaxNum = 180;
	@Value("${case.img.dir}")
	private String caseImgDir;
	@Value("${submit.error.stop.flag}")
	private String submitErrorStopFlag;
	@Value("${click.cms.xpath}")
	private String clickCmsXpath;
	@Value("${click.bms.xpath}")
	private String clickBmsXpath;
	@Value("${click.lms.xpath}")
	private String clickLmsXpath;
	@Value("${click.fms.xpath}")
	private String clickFmsXpath;
	@Value("${max.retry.num}")
	private int maxRetryNum = 3;
	@Value("${changeEodDay.style}")
	private String changeEodDayStyle;
	@Value("${click.confirm.xpath}")
	private String clickConfirmXpath;
	@Value("${step.task.times.flag}")
	private String stepTaskTimesFlag;
	@Autowired
	private JdbcHandler jdbcHandler;
	@Autowired
	private CaseExtDao caseExtDao;
	@Autowired
	private CaseConfigManager caseConfigManager;
	private static String format = "yyyy-MM-dd";
	private static Pattern compile = Pattern.compile("\\d+(\\.\\d+)*");
	private FelEngine fel = new FelEngineImpl();

	/**
	 * @author chen.pian
	 * @param caseConfig
	 */
	public void handle(CaseConfig caseConfig) {
		caseConfig.changeStatus(AutoTestConstants.RUN, false);
		logger.info("开始执行case:" + caseConfig.getCaseName());
		caseConfig.setErrorMsg(null);
		caseConfig.setErrorImgName(null);
		caseConfig.clearStepInfos();
		caseConfig.setInfos(JSON.toJSONString(caseConfig.getStepInfos()));
		caseExtDao.updateCaseInfo((Case) caseConfig);
		caseConfig.add("开始执行case:" + caseConfig.getCaseName());
		String prefixUrl = caseConfig.getSettingMap().get("prefixUrl");
		ChromeDriver driver = new ChromeDriver();
		driver.manage().timeouts().implicitlyWait(implicitlyWait, TimeUnit.SECONDS);
		driver.manage().window().maximize();
		WebDriverWait wait = new WebDriverWait(driver, unitWait);
		long startTime = System.currentTimeMillis();
		try {
			List<Step> stepList = caseConfig.getStepList();
			if (stepList == null || stepList.size() <= 0) {
				throw new RuntimeException("没有step");
			}
			int index = 0;
			Step step = null;
			Type type = null;
			for (int i = 0; i < stepList.size(); i++) {
				long stepStartTime = System.currentTimeMillis();
				try {
					if (caseConfig.isForceStop()) {
						throw new BusException("强制结束");
					}
					step = stepList.get(i);
					index++;
					this.logInfo(step, caseConfig, index);
					type = step.getType();
					if (Type.url == type || Type.queryWaitsql == type || Type.updateSql == type) {
						if (Type.url == type) {
							this.toUrl(prefixUrl, driver, step, wait, caseConfig, index);
						} else if (Type.queryWaitsql == type) {
							step.getCountRunSql().set(0);
							this.queryWaitsql(step);
						} else if (Type.updateSql == type) {
							jdbcHandler.update(step);
						}
						if (step.isRefresh()) {
							driver.navigate().refresh();
						}
					} else {
						if (!StringUtils.hasText(step.getXpath())) {
							Assert.notNull(step.getXpath(), "信息如下:" + step.toString());
						}
						WebElement ele = driver.findElementByXPath(step.getXpath());
						if (Type.click == type || Type.radio == type || Type.submit == type) {
							this.click(ele, step, caseConfig, driver, index);
						} else if (Type.input == type) {
							this.input(ele, step, driver);
						} else if (Type.date == type) {
							this.date(ele, step, driver);
						} else if (Type.select == type) {
							this.select(ele, step);
						} else if (Type.validate == type) {
							this.validate(ele, step, caseConfig, index);
						} else if (Type.validateEod == type) {
							i = this.validateEod(ele, step, caseConfig, index, i);
						}
						if (step.isAfterWait()) {
							this.waitUnit(driver, wait, caseConfig);
						}
					}
					step.getRetryNum().set(0);
				} catch (Throwable e) {
					logger.error("error", e);
					int tempi = this.retryHandle(e, step, i);
					if (tempi == -1) {
						continue;
					} else {
						i = tempi;
					}
				}
				calcUseTimes(stepStartTime, index, caseConfig);
			}
			caseConfig.setStatus(AutoTestConstants.SUCCESS);
		} catch (Throwable e) {
			logger.error("error", e);
			this.errorHandle(driver, e, caseConfig);
		} finally {
			long endTime = System.currentTimeMillis();
			double useTimes = (endTime - startTime) * 1.0 / 1000;
			caseConfig.add("完成,耗时" + useTimes + "s");
			caseConfig.setUseTimes(useTimes);
			caseConfig.setLastRunTime(endTime);
			caseConfig.setInfos(JSON.toJSONString(caseConfig.getStepInfos()));
			caseExtDao.updateCaseInfo((Case) caseConfig);
			driver.quit();
			caseConfig.setForceStop(false);
		}
	}

	/**
	 * @author chen.pian
	 * @param stepStartTime
	 * @param index
	 * @param caseConfig
	 */
	private void calcUseTimes(long stepStartTime, int index, CaseConfig caseConfig) {
		if ("Y".equals(stepTaskTimesFlag)) {
			long stepEndTime = System.currentTimeMillis();
			double useTimes = (stepEndTime - stepStartTime) * 1.0 / 1000;
			caseConfig.add("执行step" + index + ":消耗" + useTimes + "s");
		}
	}

	/**
	 * @author chen.pian
	 * @param driver
	 * @param e
	 * @param caseConfig
	 */
	private void errorHandle(ChromeDriver driver, Throwable e, CaseConfig caseConfig) {
		caseConfig.add("执行出错:" + e.getMessage());
		caseConfig.setErrorMsg(e.getMessage().replaceAll("<", "").replaceAll(">", ""));
		caseConfig.setStatus(AutoTestConstants.FAIL);
		caseConfig.setErrorImgName(this.takePhotos(driver, caseConfig));
	}

	/**
	 * @author chen.pian
	 * @param driver
	 * @param caseConfig
	 */
	private String takePhotos(ChromeDriver driver, CaseConfig caseConfig) {
		try {
			File scrFile = driver.getScreenshotAs(OutputType.FILE);
			File desFile = new File(caseImgDir + File.separator + UUID.randomUUID().toString() + ".png");
			FileUtils.copyFile(scrFile, desFile);
			return desFile.getName();
		} catch (IOException ex) {
			ex.printStackTrace();
		}
		return null;
	}

	/**
	 * @author chen.pian
	 * @param ele
	 * @param step
	 * @param caseConfig
	 * @param index
	 */
	private void logInfo(Step step, CaseConfig caseConfig, int index) {
		String value = step.getValue();
		value = value == null ? "" : ",值为" + value;
		int reTry = step.getRetryNum().get();
		String retryStr = "";
		if (reTry > 0) {
			retryStr = ",重试" + String.valueOf(reTry);
		}
		logger.info("执行step" + index + ":" + step.getType().getName() + step.getDesc() + value + retryStr);
		caseConfig.add("执行step" + index + ":" + step.getType().getName() + step.getDesc() + value + retryStr);
	}

	/**
	 * @author chen.pian
	 * @param ele
	 * @param step
	 * @param driver
	 */
	private void input(WebElement ele, Step step, ChromeDriver driver) {
		driver.executeScript("arguments[0].value = null", ele);
		String wishValue = step.getValue().trim();
		ele.sendKeys(step.getValue());
		BigDecimal wishAmt = isNum(wishValue);
		String value = ele.getAttribute("value");
		if (wishAmt != null) {
			value = value.replaceAll(",", "");
			if (wishAmt.compareTo(new BigDecimal(value)) != 0) {
				throw new RuntimeException("输入没有成功");
			}
		} else {
			if (!wishValue.equals(value)) {
				throw new RuntimeException("输入没有成功");
			}
		}
	}

	/**
	 * 
	 * @author chen.pian
	 * @param ele
	 * @param step
	 */
	private void select(WebElement ele, Step step) {
		Select select = new Select(ele);
		select.selectByVisibleText(step.getValue());
	}

	/**
	 * 
	 * @author chen.pian
	 * @param ele
	 * @param step
	 * @param driver
	 */
	private void date(WebElement ele, Step step, ChromeDriver driver) {
		driver.executeScript("arguments[0].value = null", ele);
		driver.executeScript("arguments[0].removeAttribute('readonly')", ele);
		driver.executeScript("arguments[0].removeAttribute('disabled')", ele);
		String wishValue = step.getValue();
		ele.sendKeys("");
		driver.executeScript("arguments[0].value = '" + wishValue + "'", ele);
		driver.executeScript("$(arguments[0]).change()", ele);
	}

	/**
	 * @author chen.pian
	 * @param e
	 * @param step
	 * @param i
	 * @return
	 * @throws Throwable
	 */
	private int retryHandle(Throwable e, Step step, int i) throws Throwable {
		if (e instanceof BusException) {
			throw e;
		}
		if (step == null) {
			throw e;
		}
		Type type = step.getType();
		try {
			step.checkRetryNum(maxRetryNum);
		} catch (Exception ex) {
			logger.error("error", ex);
			if (Type.submit == type) {
				return -1;
			} else {
				throw ex;
			}
		}
		try {
			Thread.sleep(retryWaitTime);
		} catch (Exception ex) {
			logger.error("error", ex);
		}
		i = i - 1;// 重试
		return i;
	}

	/**
	 * @author chen.pian
	 * @param ele
	 * @param step
	 * @param driver
	 */
	private void click(WebElement ele, Step step, CaseConfig caseConfig, ChromeDriver driver, int index) {
		String display = ele.getCssValue("display");
		logger.info("==============display================" + display);
		if ("none".equals(display)) {
			driver.executeScript("arguments[0].style.display = 'block'", ele);
		}
		ele.click();
		if (Type.submit == step.getType()) {
			try {
				Thread.sleep(submitWaitCamera);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			caseConfig.add("执行step" + index + ":提交截图", this.takePhotos(driver, caseConfig));
			if ("Y".equals(submitErrorStopFlag)) {
				List<WebElement> eles = driver.findElements(By.className("toast-error"));
				if (eles != null && eles.size() > 0) {
					throw new BusException("提交失败,请查看浏览器截图");
				}
			}
			try {
				ele = driver.findElementByXPath(clickConfirmXpath);
			} catch (Exception e) {
				ele = null;
			}
			if (ele != null) {
				ele.click();
			}
		}
	}

	/**
	 * @author chen.pian
	 * @param ele
	 * @param step
	 * @param caseConfig
	 * @param index
	 * @param i
	 * @return
	 * @throws ParseException
	 */
	private int validateEod(WebElement ele, Step step, CaseConfig caseConfig, int index, int i) throws ParseException {
		String text = ele.getText();
		text = text.replaceAll("业务日期：", "");
		logger.info("###validateEod###" + text);
		Date nowDate = DateUtils.parseDate(text, format);
		try {
			Date wishDate = DateUtils.parseDate(step.getValue(), format);
			if (!DateUtils.isSameDay(nowDate, wishDate)) {
				caseConfig.add("执行step" + index + ":重新修改EOD,当前时间为" + text + ",期望时间为" + step.getValue());
				if ("P".equals(changeEodDayStyle)) {
					i = i - 7;// 重新修改EODdate
				} else {
					i = i - 2;
				}
			}
		} catch (ParseException e) {
			throw new BusException(e);
		}
		return i;
	}

	/**
	 * @author chen.pian
	 * @param wishValue
	 * @return
	 */
	private BigDecimal isNum(String wishValue) {
		BigDecimal wishAmt = null;
		try {
			wishValue = wishValue.trim().replaceAll(",", "");
			wishAmt = new BigDecimal(wishValue);
		} catch (Exception e) {

		}
		return wishAmt;
	}

	/**
	 * @author chen.pian
	 * @param ele
	 * @param step
	 * @param caseConfig
	 * @param index
	 */
	private void validate(WebElement ele, Step step, CaseConfig caseConfig, int index) {
		String text = ele.getText().trim();
		String wishValue = step.getValue().trim();
		BigDecimal wishAmt = isNum(wishValue);
		boolean pass = false;
		if (wishAmt != null) {
			text = text.trim().replaceAll(",", "");
			logger.error("validate text=" + text);
			Matcher matcher = compile.matcher(text);
			matcher.find();
			text = matcher.group();// 提取匹配到的结果
			if (wishAmt.compareTo(new BigDecimal(text)) == 0) {
				pass = true;
			}
		} else {
			try {
				int temp = text.lastIndexOf("：");
				if (temp != -1) {
					text = text.substring(temp + 1);
				}
				temp = text.lastIndexOf(":");
				if (temp != -1) {
					text = text.substring(temp + 1);
				}
			} catch (Exception e) {

			}
			text = text.trim();
			if (text.equals(wishValue)) {
				pass = true;
			}
		}
		if (pass) {
			caseConfig.add("执行step" + index + ":验证通过,值为" + text);
		} else {
			throw new BusException("验证失败,验证" + step.getDesc() + "期望值为" + wishValue + ",实际值为" + text);
		}
	}

	/**
	 * @author chen.pian
	 * @param step
	 */
	private void queryWaitsql(Step step) {
		FelContext ctx = fel.getContext();
		step.getCountRunSql().getAndIncrement();
		Map<String, Object> dataMap = jdbcHandler.query(step);
		Set<Entry<String, Object>> set = dataMap.entrySet();
		for (Entry<String, Object> entry : set) {
			ctx.set(entry.getKey(), entry.getValue());
		}
		Object obj = fel.eval(step.getCondition());
		if (!Boolean.valueOf(obj.toString())) {
			try {
				Thread.sleep(retryWaitTime);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if (step.getCountRunSql().get() >= maxRetrySqlNum) {
				throw new RuntimeException("等到执行超时");
			}
			queryWaitsql(step);
		} else {
			logger.info("成功执行完job");
		}
	}

	/**
	 * @author chen.pian
	 * @param prefixUrl
	 * @param driver
	 * @param step
	 * @param ele
	 * @param wait
	 * @param caseConfig
	 */

	public void toUrl(String prefixUrl, ChromeDriver driver, Step step, WebDriverWait wait, CaseConfig caseConfig,
			int index) {
		if (!isCorrectGotoModule(prefixUrl, driver, step)) {
			this.waitGotoModule(prefixUrl, driver, step);
		}
		String toUrl = prefixUrl + step.getUrl();
		driver.navigate().to(toUrl);
		if (!driver.getCurrentUrl().equals(toUrl)) {
			caseConfig.add("执行step" + index + ":定位" + toUrl + "失败");
			throw new RuntimeException("定位" + toUrl + "失败");
		}
	}

	/**
	 * @author chen.pian
	 * @param driver
	 * @param step
	 */
	private void clickModule(ChromeDriver driver, Step step) {
		String url = step.getUrl().substring(0, 4);
		WebElement ele = null;
		if (url.startsWith("/cms")) {
			ele = driver.findElementByXPath(clickCmsXpath);
			ele.click();
		} else if (url.startsWith("/bms")) {
			ele = driver.findElementByXPath(clickBmsXpath);
			ele.click();
		} else if (url.startsWith("/fms")) {
			ele = driver.findElementByXPath(clickFmsXpath);
			ele.click();
		} else if (url.startsWith("/lms")) {
			ele = driver.findElementByXPath(clickLmsXpath);
			ele.click();
		}
	}

	/**
	 * @author chen.pian
	 * @param prefixUrl
	 * @param driver
	 * @param step
	 */
	private void waitGotoModule(String prefixUrl, ChromeDriver driver, Step step) {
		int count = 1;
		while (true) {
			if (isCorrectGotoModule(prefixUrl, driver, step)) {
				return;
			} else {
				this.clickModule(driver, step);
			}
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if (count > skipModuleTryMaxNum) {
				throw new RuntimeException("跳转模块失败");
			}
			count++;
		}
	}

	/**
	 * @author chen.pian
	 * @param prefixUrl
	 * @param driver
	 * @param step
	 * @return
	 */
	private boolean isCorrectGotoModule(String prefixUrl, ChromeDriver driver, Step step) {
		String currentUrl = driver.getCurrentUrl();
		if (currentUrl.startsWith(prefixUrl)) {
			currentUrl = currentUrl.substring(prefixUrl.length());
			currentUrl = currentUrl.substring(0, 4);
		}
		String url = step.getUrl().substring(0, 4);
		if (!url.equals(currentUrl) && !step.isLogin()) {
			return false;
		}
		return true;
	}

	/**
	 * @author chen.pian
	 * @param driver
	 * @param wait
	 * @param caseConfig
	 */
	public void waitUnit(ChromeDriver driver, WebDriverWait wait, CaseConfig caseConfig) {
		driver.manage().timeouts().implicitlyWait(unitWait, TimeUnit.SECONDS);
		wait.until(new ExpectedCondition<WebElement>() {
			@Override
			public WebElement apply(WebDriver d) {
				WebElement ele = null;
				try {
					ele = d.findElement(By.className("layui-layer-shade"));
				} catch (Exception e) {

				}
				if (ele == null) {
					return d.findElement(By.tagName("html"));
				}
				return null;
			}
		});
		driver.manage().timeouts().implicitlyWait(implicitlyWait, TimeUnit.SECONDS);
	}
}
